// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_mesh.h"
#include "mk_mesh_data_loader.h"
#include "mk_skeleton.h"
#include "mk_skinning.h"
#include "mk_error.h"
#include "mk_math.h"
#include "mk_engine.h"
#include "mk_memory_block.h"
#include "mk_file_system.h"

namespace MK3DE
{
	CMesh::CMesh(const char* fileName) : CResource(fileName)
	{
		__Clean();

		MaterialIdx = 0;
	}

	CMesh::~CMesh()
	{
	}

	void CMesh::__Clean()
	{
		_header = NULL;
		_positions = NULL;
		_normals = NULL;
		_colors = NULL;
		_texCoord1 = NULL;
		_texCoord2 = NULL;
		_texCoord3 = NULL;
		_skins = NULL;
		_faces = NULL;
	}

	BOOL CMesh::Load()
	{
		BOOL ret = FALSE;
		
		ret = GetEngine()->GetFileSystem()->ReadFile(_fileName.c_str(), &_dataBlock);
		MK_PROCESS_ERROR(ret);

		BYTE* data = _dataBlock.GetBuffer();
		_header = (Header*)data;
		MK_PROCESS_ERROR(_header->id == MK_MESH_ID);

		_positions = (Position*)(data + _header->offsetPosition);
		MK_PROCESS_ERROR((_header->offsetPosition + (sizeof(Position) * _header->vertexNum)) <= _dataBlock.GetSize());

		_normals = _header->offsetNormal == 0 ? NULL : (Normal*)(data + _header->offsetNormal);
		MK_PROCESS_ERROR((_header->offsetNormal + (sizeof(Normal) * _header->vertexNum)) <= _dataBlock.GetSize());

		_colors = _header->offsetColor == 0 ? NULL : (Color*)(data + _header->offsetColor);
		MK_PROCESS_ERROR((_header->offsetColor + (sizeof(Color) * _header->vertexNum)) <= _dataBlock.GetSize());

		_texCoord1 = _header->offsetTexCoord1 == 0 ? NULL : (TextureCoord*)(data + _header->offsetTexCoord1);
		MK_PROCESS_ERROR((_header->offsetTexCoord1 + (sizeof(TextureCoord) * _header->vertexNum)) <= _dataBlock.GetSize());

		_texCoord2 = _header->offsetTexCoord2 == 0 ? NULL : (TextureCoord*)(data + _header->offsetTexCoord2);
		MK_PROCESS_ERROR((_header->offsetTexCoord2 + (sizeof(TextureCoord) * _header->vertexNum)) <= _dataBlock.GetSize());

		_texCoord3 = _header->offsetTexCoord3 == 0 ? NULL : (TextureCoord*)(data + _header->offsetTexCoord3);
		MK_PROCESS_ERROR((_header->offsetTexCoord3 + (sizeof(TextureCoord) * _header->vertexNum)) <= _dataBlock.GetSize());

		_skins = _header->offsetSkin == 0 ? NULL : (Influence*)(data + _header->offsetSkin);
		MK_PROCESS_ERROR((_header->offsetSkin + (sizeof(Influence) * _header->vertexNum)) <= _dataBlock.GetSize());

		_faces = _header->offsetFace == 0 ? NULL : (Face*)(data + _header->offsetFace);
		MK_PROCESS_ERROR((_header->offsetFace + (sizeof(Face) * _header->faceNum)) <= _dataBlock.GetSize());		

		UpdateAABB();

		ret = TRUE;
Exit0:
		if (!ret)
			__Clean();

		return ret;
	}

	void CMesh::UpdateAABB()
	{
		for (DWORD i = 0; i < _header->vertexNum; ++i)
		{
			D3DXVECTOR3& pos = _positions[i];
			_aabb.Merge(&pos);
		}
	}

	BOOL CMesh::FillVertexBuffer(CSkeleton* pSkeleton, CMesh::Position* pPosition, 
		CMesh::Normal* pNormal, DWORD vertexNum)
	{
		if (vertexNum > _header->vertexNum)
			return FALSE;

		if (IsSkinMesh())
		{
			if (!pSkeleton)
				return FALSE;

			return CSkinning::BlendVertex(_positions, _normals, _skins, pSkeleton, pPosition, pNormal, _header->vertexNum);
		}
		else
		{
			memcpy(pPosition, _positions, sizeof(Position) * vertexNum);
			memcpy(pNormal, _normals, sizeof(Normal) * vertexNum);
			return TRUE;
		}
	}

	BOOL CMesh::FillUVBuffer(CMesh::TextureCoord* pUV, DWORD vertexNum)
	{
		memcpy(pUV, _texCoord1, sizeof(TextureCoord) * vertexNum);
		return TRUE;
	}

	BOOL CMesh::FillSkinBuffer(D3DXVECTOR4* pIndex, D3DXVECTOR4* pWeight, DWORD vertexNum)
	{
		if (!IsSkinMesh())
			return FALSE;

		for (DWORD i = 0; i < vertexNum; i++)
		{
			CMesh::Influence& inf = _skins[i];
			float index[4] = {0, 0, 0, 0};
			float weight[4] = {0, 0, 0, 0};
			float* pi = index;
			float* pw = weight;
			for (int j = 0; j < 4; ++j)
			{
				if (inf.boneIdx[j] == 0xff)
					break;

				if (abs(inf.weight[j]) > MK_EPSILON)
				{
					*pi++ = (float)inf.boneIdx[j];
					*pw++ = (float)inf.weight[j];
				}
			}

			*pIndex++ = D3DXVECTOR4(index);
			*pWeight++ = D3DXVECTOR4(weight);
		}

		return TRUE;
	}


	BOOL CMesh::FillFaceBuffer(CMesh::Face* pFace, DWORD faceNum, DWORD idx_offset)
	{
		if (faceNum > _header->faceNum)
			return FALSE;

		if (idx_offset == 0)
		{
			memcpy(pFace, _faces, sizeof(CMesh::Face) * faceNum);
		}
		else
		{
			for (DWORD i = 0; i < _header->faceNum; ++i)
			{
				CMesh::Face& face = _faces[i];
				pFace->vertexIndex[0] = idx_offset + face.vertexIndex[0];
				pFace->vertexIndex[1] = idx_offset + face.vertexIndex[1];
				pFace->vertexIndex[2] = idx_offset + face.vertexIndex[2];

				pFace++;
			}
		}

		return TRUE;
	}

	BOOL CMesh::CollideWithRay(D3DXMATRIX& mat, D3DXVECTOR3& pos, float x, float y, float z, D3DXVECTOR3& dir)
	{
		AABB aabb;
		TransformAABB(&aabb, &_aabb, &mat);

		if (!AABBCollisionWithRay(pos, aabb, x, y, z, dir))
			return FALSE;

		for (DWORD i = 0; i < _header->faceNum; ++i)
		{
			Face& face = _faces[i];
			D3DXVECTOR3 v1 = _positions[face.vertexIndex[0]];
			D3DXVECTOR3 v2 = _positions[face.vertexIndex[1]];
			D3DXVECTOR3 v3 = _positions[face.vertexIndex[2]];

			D3DXVec3TransformCoord(&v1, &v1, &mat);
			D3DXVec3TransformCoord(&v2, &v2, &mat);
			D3DXVec3TransformCoord(&v3, &v3, &mat);

			if (RayIntersectTriangle(pos, D3DXVECTOR3(x, y, z), dir, v1, v2, v3))
				return TRUE;
		}

		return FALSE;
	}
}